﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Messaging;
using System.IO;
using System.Runtime.Serialization.Formatters.Soap;
using Common; // where command class is
using System.Configuration;
using System.Diagnostics;

namespace GameServer
{
	public class Server : IDisposable 
	{
		//properties
		public static string QueueName
		{
			get { return _queuename; }
			set
			{
				if (_server == null)
					_queuename = value;
			}
		}
		public static int BufferSize
		{
			get { return _bufferSize; }
			set
			{
				if (_server == null)
					_bufferSize = value;
			}
		}
		public static int AliveInterval
		{
			get { return _aliveInterval; }
			set
			{
				if (_server == null)
					_aliveInterval = value;
			}
		}
        
		//private
		private static int _bufferSize = 400; // has to divide by 2
        private static string _queuename;
		private static int _aliveInterval = 3000;
		private static System.Timers.Timer AliveTimer;

		private TcpListener _serverListener;
        private static Dictionary<string, SubsClient> _connections;
		private static Server _server = null;
		private int _port;
		private bool _isServerActive = false;//server is false
		private MessageQueue _commandQueue;
		private object _lockObj = new object();// for lock
		private static object _lockObject = new object();// to lock connection list
		private static object _singletonObject = new object(); // to lock singleton creation
		private static EventLog _eventLog;


		//get server singleton
		public static Server GetServerSingleton(int port)
		{

			lock (_singletonObject)//to prevent several calls to this method
			{
				if (_server == null)
				{
					_server = new Server(port);
				}
				return _server;
			}
		}
		public static Server GetServerSingleton()
		{
			object lockObject = new object();
			lock (lockObject)//to prevent several calls to this method
			{
				if (_server == null)
				{
					_server = new Server(80);
				}
				return _server;
			}
		}
		private Server(int port)
		{
			//change server status to active
			isServerActive = true;// change server's status

			//get or create message queue
			string queueName = QueueName;
			if (MessageQueue.Exists(queueName)) // check if queue already exists
				_commandQueue = new MessageQueue(queueName);
			else // otherwise create it
				_commandQueue = MessageQueue.Create(queueName, false);


            _connections = new Dictionary<string, SubsClient>();
			_port = port;//save the port for the Run function to listen on
			Thread listenThread = new Thread(new ThreadStart(RunListener)); // creates a new Thread for the server to listen in
			listenThread.Start();//start the Thread
			Thread dataThread = new Thread(new ThreadStart(RunListenForData)); // creates a new Thread for the server to listen in
			dataThread.Start();//start the Thread
			_eventLog = new EventLog("BS Server Log", ".", "BS");
			_eventLog.WriteEntry("Server started", EventLogEntryType.Information);
            //Timer to send Alive messages to SubClients
			AliveTimer = new System.Timers.Timer(AliveInterval);
            AliveTimer.Elapsed += new System.Timers.ElapsedEventHandler(AliveTimer_Elapsed);
			AliveTimer.Start();
		}

        void AliveTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
			string userName;
			for(int i=0;i<GetConnections().Count;i++)
			{
				userName = GetConnections().ToList()[i].Key;
				if (userName != "")
				{
					if (GetConnections()[userName].AliveResponse < 5)
						GetConnections()[userName].AliveResponse++;
					else
					{
						RemoveConnection(userName);
						_eventLog.WriteEntry("Client <" + userName + "> was disconnected", EventLogEntryType.Warning);
					}
					try
					{
						sendCommand(userName, new ServerCommand(userName));
					}
					catch
					{
						RemoveConnection(userName);
					}
				}
			}
        }

		private void RunListenForData() // iterates on each connection and gets data
		{
			while (isServerActive)
			{
				object lockObj = new object();
				lock (lockObj)
				{
					for (int i = 0; i < _connections.Count; i++)
					{
                        SubsClient currClient = GetConnections().Values.ToList()[i];
						try
						{
							bool checkData = currClient.Connection.GetStream().DataAvailable;
							if (checkData)
							{
								// Get first command from TcpClient stream
								ClientCommand command = General.GetCommandFromStream(currClient.Connection.GetStream()) as ClientCommand;
								if (command != null)
								{
									_commandQueue.Send(command); //send to message queue
									_eventLog.WriteEntry("Command " + command.ComTypeClient.ToString() + " was received from client <" + GetConnections().Keys.ToList()[i] + ">", EventLogEntryType.Information);
								}
								else
								{
									_eventLog.WriteEntry("Can not read command from client <" + GetConnections().Keys.ToList()[i] + ">", EventLogEntryType.Error);
								}
							}
						}
						catch (Exception)
						{
							// Client was disconnected
							RemoveConnection(GetConnections().Keys.ToList()[i]);
						}
					}
				}
				Thread.Sleep(100);
			}
		}
		private static void VerifyConnection(object connection)
		{
			const int timeout = 5000;
			int timepassed = 0;
			SubsClient client = new SubsClient(connection as TcpClient);
			while (timepassed < timeout)
			{
				//verify connection
				if (client != null && client.Connection != null && client.Connection.Connected)
				{
					if (client.Connection.GetStream().DataAvailable)
					{
						// Get first command from TcpClient stream
						ClientCommand command = General.GetCommandFromStream(client.Connection.GetStream()) as ClientCommand;
						if (command != null)
							_eventLog.WriteEntry("Login command received", EventLogEntryType.SuccessAudit);
						else
							_eventLog.WriteEntry("Can't read login command", EventLogEntryType.FailureAudit);
						//TODO: validate through webservice
						if (command.ComTypeClient == ClientCommandType.Login /* && validate from ws*/)
						{
							_server.sendCommand(client, new ServerCommand(ClientCommandType.Login, command.User, true));
							GetConnections().Add(command.User, client);
							RaiseServerEvent(ClientCommandType.Login, command.User);
							_eventLog.WriteEntry("Client <" + command.User + "> logged in", EventLogEntryType.Information);
						}
						else
						{
							_server.sendCommand(client, new ServerCommand(ClientCommandType.Login, "", false));
							_eventLog.WriteEntry("Wrong login for client <" + command.User + ">", EventLogEntryType.Warning);
						}
						break;
					}
				}
				else
					return;
				timepassed += 5;
				Thread.Sleep(5);
			}
		}

		//public functions
        public static Dictionary<string, SubsClient> GetConnections()
		{
			lock (_lockObject)
			{
				return _connections;
			}
		}
		public void RemoveConnection(string name)
		{
			SubsClient _toRemove = findClientByName(name);
			if (_toRemove != null)
			{
				if (_toRemove.Connection != null)
					_toRemove.Connection.GetStream().Close();
				GetConnections().Remove(name);
				_eventLog.WriteEntry("Client <" + name + "> was disconnected", EventLogEntryType.Information);
			}
		}
		//server send command functions
		public void sendCommand(SubsClient destination, ServerCommand command)
		{
			byte[] commandData = General.BuildResponse(command);
				destination.Connection.GetStream().Write(commandData, 0, commandData.Length);
		}
		public void sendCommand(string Name, ServerCommand command)
		{
			SubsClient destination = findClientByName(Name);
			if (destination != null)//check if the returned value is populated
			{
				sendCommand(destination, command);
			}
		}
		public SubsClient findClientByName(string Name)
		{
			return GetConnections()[Name];
		}
		//properties
		public bool isServerActive
		{
			get { return _isServerActive; }
			private set { _isServerActive = value; }
		}

		private void RunListener()//listens for connections
		{
			_serverListener = new TcpListener(IPAddress.Parse("127.0.0.1"), _port);
			_serverListener.Start();// start listening on _port
			while (isServerActive)
			{
				try
				{
					TcpClient tcpClient = _serverListener.AcceptTcpClient();
					_eventLog.WriteEntry("New connection received", EventLogEntryType.Information);
					ThreadPool.QueueUserWorkItem(new WaitCallback(VerifyConnection), tcpClient); //TODO: fix this
				}
				catch(Exception e)
				{
					_eventLog.WriteEntry("Server is not active!", EventLogEntryType.Error);
					break;
				}
			}

		}


		//on destruct
		public void Dispose()
		{
			AliveTimer.Stop();
			_serverListener.Stop();// stop the server
			_eventLog.WriteEntry("Server stopped", EventLogEntryType.Information);

			foreach (SubsClient c in GetConnections().Values)//iterate on connections and close the streams
				c.Connection.GetStream().Close();

			GetConnections().Clear();

			//call GC
			GC.Collect();
		}

		//Events (for testing)
		#region Events
		
		// Delegate
		public delegate void ServerReceiveHandler(object sender, ServerEventArgs e);

		// Event
		public static event ServerReceiveHandler Login;

		// Event raise
		private static void RaiseServerEvent(ClientCommandType commandType, params object[] arguments)
		{
			switch (commandType)
			{
				case ClientCommandType.Login:
					if (Login != null)
					{
						Login(null, new ServerEventArgs(arguments[0].ToString()));// arguments[0] is Username
					}
					break;
				default:
					break;
			}
		}

		#endregion

	}

	public class ServerEventArgs : EventArgs
	{
		public bool BoolValue { get; private set; }
		public int ColValue { get; private set; }
		public int RowValue { get; private set; }
		public string StringValue { get; private set; }

		public ServerEventArgs(string msg) : this(false, -1, -1, msg) { }
		public ServerEventArgs(bool bVal) : this(bVal, -1, -1, "") { }
		public ServerEventArgs(int col, int row) : this(false, col, row, "") { }
		private ServerEventArgs(bool bval, int col, int row, string msg)
		{
			BoolValue = bval;
			ColValue = col;
			RowValue = row;
			StringValue = msg;
		}
	}
}